package com.atguigu.gmall.item.service.impl;

import com.atguigu.gmall.common.constant.SysRedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.util.Jsons;
import com.atguigu.gmall.feign.product.SkuProductFeignClient;
import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.model.product.SkuImage;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.model.product.SpuSaleAttr;
import com.atguigu.gmall.model.to.CategoryViewTo;
import com.atguigu.gmall.model.to.SkuDetailTo;
import com.atguigu.starter.cache.annocation.GmallCache;
import com.atguigu.starter.cache.service.CacheOpsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;


@Service
@Slf4j
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    SkuProductFeignClient skuDetailFeignClient;

    @Autowired
    private ThreadPoolExecutor executor;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    CacheOpsService cacheOpsService;

    Map<Long, ReentrantLock> lockPool = new ConcurrentHashMap<>();


    //private Map<Long,SkuDetailTo> cache = new HashMap<>();

    public SkuDetailTo getSkuDetailFromRpc(Long skuId) {
        SkuDetailTo skuDetailTo = new SkuDetailTo();

        //1.查询基本信息
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            Result<SkuInfo> result = skuDetailFeignClient.getSkuInfo(skuId);
            SkuInfo skuInfo = result.getData();
            skuDetailTo.setSkuInfo(skuInfo);
            return skuInfo;
        }, executor);

        //2.查看商品图片信息
        CompletableFuture<Void> imageFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null){
                Result<List<SkuImage>> skuImages = skuDetailFeignClient.getSkuImages(skuId);
                skuInfo.setSkuImageList(skuImages.getData());
            }
        }, executor);


        //3、查看实时价格
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            Result<BigDecimal> sku1010Price = skuDetailFeignClient.getSku1010Price(skuId);
            skuDetailTo.setPrice(sku1010Price.getData());
        }, executor);

        //4.查看销售属性名和值
        CompletableFuture<Void> saleAttrFuture = skuInfoFuture.thenAcceptAsync((skuInfo) -> {
            if (skuInfo != null){
                Result<List<SpuSaleAttr>> skuSaleattrvalues = skuDetailFeignClient.getSkuSaleattrvalues(skuId, skuInfo.getSpuId());
                skuDetailTo.setSpuSaleAttrList(skuSaleattrvalues.getData());
            }
        }, executor);


        //5.查sku组合
        CompletableFuture<Void> skuValueFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null){
                Result<String> skuValueJson = skuDetailFeignClient.getSKuValueJson(skuInfo.getSpuId());
                skuDetailTo.setValuesSkuJson(skuValueJson.getData());
            }
        }, executor);


        //6.查分类
        CompletableFuture<Void> categoryFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null){
                Result<CategoryViewTo> categoryView = skuDetailFeignClient.getCategoryView(skuInfo.getCategory3Id());
                skuDetailTo.setCategoryView(categoryView.getData());
            }
        }, executor);

        CompletableFuture.allOf(imageFuture, priceFuture, saleAttrFuture, skuValueFuture, categoryFuture).join();

        return skuDetailTo;
    }

    @GmallCache(cacheKey = SysRedisConst.SKU_INFO_PREFIX + "#{#params[0]}",
                bloomKey = SysRedisConst.BLOOM_SKUID,
                bloomValue = "#{#params[0]}",
                lockName = SysRedisConst.LOCK_SKU_DETAIL + "#{#params[0]}",
                ttl = 60*60*24*7L)
    @Override
    public SkuDetailTo getSkuDetail(Long skuId) {
        SkuDetailTo skuDetailFromRpc = getSkuDetailFromRpc(skuId);
        return skuDetailFromRpc;
    }


    /**
     * 分布式锁+布隆过滤器
     * @param skuId
     * @return
     */
    public SkuDetailTo getSkuDetailWhtiCache(Long skuId){
        String cacheKey = SysRedisConst.SKU_INFO_PREFIX + skuId;
        //1、先查缓存
        SkuDetailTo cacheData = cacheOpsService.getCacheData(cacheKey,SkuDetailTo.class);
        //2、判断
        if (cacheData == null){
            //3、缓存没有
            //4、先问布隆过滤器
            boolean contain = cacheOpsService.bloomContains(skuId);
            if (!contain){
                //5、布隆说没有就一定没有
                log.info("[{}]商品 - 布隆判定没有，检测到隐藏的攻击风险" + skuId);
                return null;
            }
            //6、布隆说有，可能有
            boolean lock = cacheOpsService.tryLock(skuId);//为当前商品添加自己的分布式锁。
            if (lock){
                //7、获取锁成功，查询远程
                SkuDetailTo fromRpc = getSkuDetailFromRpc(skuId);
                //8、将远程查出来的数据放入缓存
                cacheOpsService.saveData(cacheKey,fromRpc);
                //9、解锁
                cacheOpsService.unlock(skuId);
                return fromRpc;
            }
            //10、没有获取到锁
            try {
                Thread.sleep(1000);
                return cacheOpsService.getCacheData(cacheKey,SkuDetailTo.class);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //4、缓存中有
        return cacheData;
    }

    public SkuDetailTo getSkuDetailxxxFuture(Long skuId) {
        lockPool.put(skuId,new ReentrantLock());
        //1、先查缓存
        String jsonStr = redisTemplate.opsForValue().get("sku:info:" + skuId);
        if ("x".equals(jsonStr)){
            //说明以前查过，只不过数据库没有此记录，为了避免再次回源，缓存占了一个占位符
            return null;
        }
            //2、判断
            if (StringUtils.isEmpty(jsonStr)) {
                //3.Redis没有缓存 就回源
                //防止随机值穿透攻击？回源之前要先用布隆或者bitmap判断有没有
                //在进行经过布隆后需要加锁，synchronized (×) 因为只需要有一个请求去回源   ReentryLock(×) 如果用微服务集群的话
                //还是会有很多请求一起去回源
                //TODO 加锁解决击穿
                SkuDetailTo fromRpc = null;
                //判断池中是否有自己的锁
                //锁池中不存在就放一把新锁，作为自己的锁，存在的话就用之前的锁
                ReentrantLock lock = lockPool.put(skuId, new ReentrantLock());

                boolean b = lock.tryLock();

                if (b){
                    fromRpc = getSkuDetailFromRpc(skuId);
                } else {
                    jsonStr =redisTemplate.opsForValue().get("sku:info:" + skuId);
                    return null;
                }
                //4.将回源查出的数据保存进redis
                String cacheJson = "x";
                if (fromRpc != null){
                    redisTemplate.opsForValue().set("sku:info:" + skuId, Jsons.toStr(fromRpc),7, TimeUnit.DAYS);
                }else {
                    redisTemplate.opsForValue().set("sku:info:" + skuId, Jsons.toStr(fromRpc),30,TimeUnit.MINUTES);
                }
                return fromRpc;
            }
        //5.将json转换为对象
        SkuDetailTo skuDetailTo = Jsons.toObj(jsonStr, SkuDetailTo.class);
        return skuDetailTo;
    }

/*    private SkuDetailTo cacheMap(Long skuId) {
        //1.先查缓存
        SkuDetailTo skuDetailToCache = cache.get(skuId);
        //2.判断
        if (skuDetailToCache == null){
            //3.缓存没有，真正的查询（回源）
            SkuDetailTo skuDetailFromRpc = getSkuDetailFromRpc(skuId);
            cache.put(skuId,skuDetailFromRpc);
            return skuDetailFromRpc;
        }
        return skuDetailToCache;
    }*/
}
